package com.atguigu.gmall.activity.controller;

import com.atguigu.gmall.activity.service.api.SeckillGoodsService;
import com.atguigu.gmall.activity.util.CacheHelper;
import com.atguigu.gmall.activity.util.DateUtil;
import com.atguigu.gmall.common.constant.RabbitConst;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.common.service.RabbitService;
import com.atguigu.gmall.common.util.AuthContextHolder;
import com.atguigu.gmall.common.util.MD5;
import com.atguigu.gmall.model.activity.OrderRecode;
import com.atguigu.gmall.model.activity.SeckillGoods;
import com.atguigu.gmall.model.activity.UserRecode;
import com.atguigu.gmall.model.order.OrderDetail;
import com.atguigu.gmall.model.order.OrderInfo;
import com.atguigu.gmall.model.user.UserAddress;
import com.atguigu.gmall.order.OrderFeignClient;
import com.atguigu.gmall.user.UserFeignClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.util.*;

/**
 * @author: 封捷
 * @create-date: 2022/9/18 17:07
 */
@RestController
@RequestMapping("/api/activity/seckill")
public class SeckillApiController {

    @Autowired
    private SeckillGoodsService seckillGoodsService;

    @Autowired
    private RabbitService rabbitService;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private OrderFeignClient orderFeignClient;

    @Autowired
    private UserFeignClient userFeignClient;

    @GetMapping("/get/all/sec/kill/goods")
    public Result<List<SeckillGoods>> getAllSeckillGoods() {
        return Result.ok(seckillGoodsService.getSeckillGoodsFromRedis());
    }

    @GetMapping("/get/sec/kill/goods/{skuId}")
    public Result<SeckillGoods> getSecKillGoodsBySkuId(@PathVariable("skuId") Long skuId) {
        return Result.ok(seckillGoodsService.getSeckillGoodsBySkuId(skuId));
    }

    @GetMapping("/auth/getSeckillSkuIdStr/{skuId}")
    public Result<String> getSeckillSkuIdStr(@PathVariable("skuId") Long skuId, HttpServletRequest request) {

        // 1、因为网关会对包含 auth 的路径进行登录检查，
        // 所以能够执行到这里的请求都是已登录请求，
        // 可以获取 userId
        String userId = AuthContextHolder.getUserId(request);

        // 2、根据 skuId 查询秒杀商品对象
        SeckillGoods seckillGoods = seckillGoodsService.getSeckillGoodsBySkuId(skuId);

        if (seckillGoods == null) {
            return Result.<String>fail().message("[系统错误]没有查询到秒杀商品，请联系系统管理员！");
        }

        // 3、获取当前商品对应的起止时间
        Date startTime = seckillGoods.getStartTime();
        Date endTime = seckillGoods.getEndTime();

        // 4、创建 Date 对象代表当前时间
        Date currentTime = new Date();

        // 5、判断当前时间是否在起止时间内
        // 可以生成下单码的逻辑条件：startTime 在 currentTime 前面 && currentTime 在 endTime 前面
        if (DateUtil.dateCompare(startTime, currentTime) && DateUtil.dateCompare(currentTime, endTime)) {
            // 6、生成下单码
            String skuIdStr = MD5.encrypt(userId);

            return Result.ok(skuIdStr);
        }

        // 7、前面条件如果都不满足，则返回失败的提示信息
        return Result.<String>fail().message("获取下单码失败！");
    }

    @PostMapping("/auth/seckillOrder/{skuId}")
    public Result<Void> seckillOrder(
            @PathVariable("skuId") Long skuId,
            @RequestParam("skuIdStr") String skuIdStr,
            HttpServletRequest request) {

        // 第一部分：排除各种不满足条件（不能进入抢购队列）的情况
        // 1、获取用户的 id
        String userId = AuthContextHolder.getUserId(request);

        // 2、排除 userId 和 skuIdStr 为空的情况（判空保护）
        if (StringUtils.isEmpty(userId) || StringUtils.isEmpty(skuIdStr)) {
            return Result.build(null, ResultCodeEnum.SECKILL_ILLEGAL);
        }

        // 3、检查下单码是否正确
        // ※说明：我们生成下单码是使用 userId 进行 MD5 加密，
        // 而 MD5 加密算法不可逆，不能把密文解密恢复为明文
        // 目前 skuIdStr 就是加密后得到的密文，不能恢复为明文，
        // 所以检查下单码是否正确需要把当前 userId 重新执行 MD5 加密，双方比较密文
        // 因为对于 MD5 加密算法（其实 MD5 只是 HASH 的一种）来说，只要输入不变，输出就不变
        // [1]把当前 userId 执行 MD5 加密
        String encrypt = MD5.encrypt(userId);

        // [2]执行密文的比较
        if (!skuIdStr.equals(encrypt)) {
            return Result.build(null, ResultCodeEnum.SECKILL_ILLEGAL);
        }

        // 4、检查当前 skuId 对于的状态位
        // [1]从当前微服务中的 Map 中读取状态位的数据
        // ※说明：其它微服务修改状态位之后，我们的状态位会被被动修改，
        // 所以我们这里拿到的就是实时数据
        Object statusPosition = CacheHelper.get(skuId.toString());

        // [2]判断
        if ("0".equals(statusPosition)) {
            return Result.build(null, ResultCodeEnum.SECKILL_FINISH);
        }

        // 第二部分：给消息队列发送消息，让用户的购买信息进入抢购队列
        // 1、封装用户购买信息对象
        UserRecode userRecode = new UserRecode();
        userRecode.setUserId(userId);
        userRecode.setSkuId(skuId);

        // 2、把用户购买信息对象发送到消息队列
        rabbitService.sendMessage(
                RabbitConst.EXCHANGE_DIRECT_SECKILL_USER,
                RabbitConst.ROUTING_SECKILL_USER,
                userRecode);

        // 3、返回操作成功
        return Result.ok();
    }

    @GetMapping("/auth/get/data/for/trade")
    public Result<Map<String, Object>> getDataForTrade(HttpServletRequest request) {

        // 1、获取登录用户的 userId
        String userId = AuthContextHolder.getUserId(request);

        // 2、从 Redis 中获取临时订单
        OrderRecode orderRecode = (OrderRecode) redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS).get(userId);

        // 3、判空保护
        if (orderRecode == null) {
            return Result.<Map<String, Object>>fail().message("您没有下单，或活动已结束！");
        }

        // 4、创建封装最终结果数据的 Map 集合
        Map<String, Object> resultMap = new HashMap<>();

        // [1]封装 userAddressList 数据
        List<UserAddress> userAddressList = userFeignClient.getUserAddressList(Long.parseLong(userId)).getData();
        resultMap.put("userAddressList", userAddressList);

        // [2]封装 detailArrayList 数据
        List<OrderDetail> detailArrayList = new ArrayList<>();
        OrderDetail orderDetail = new OrderDetail();

        orderDetail.setSkuId(orderRecode.getSeckillGoods().getSkuId());
        orderDetail.setSkuName(orderRecode.getSeckillGoods().getSkuName());
        orderDetail.setImgUrl(orderRecode.getSeckillGoods().getSkuDefaultImg());
        orderDetail.setOrderPrice(orderRecode.getSeckillGoods().getCostPrice());
        orderDetail.setSkuNum(orderRecode.getNum());

        detailArrayList.add(orderDetail);

        resultMap.put("detailArrayList", detailArrayList);

        // [3]封装 totalNum 数据
        resultMap.put("totalNum", orderRecode.getNum());

        // [4]封装 totalAmount 数据
        BigDecimal costPrice = orderRecode.getSeckillGoods().getCostPrice();
        resultMap.put("totalAmount", costPrice.doubleValue());

        return Result.ok(resultMap);
    }

    @PostMapping("/auth/submitOrder")
    public Result<Long> submitOrder(@RequestBody OrderInfo orderInfo, HttpServletRequest request) {

        String userId = AuthContextHolder.getUserId(request);

        // 如果我们要保存的订单在 Redis 中已经存在，则返回错误提示信息
        OrderRecode orderRecode =
                (OrderRecode) redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS).get(userId);
        if (null == orderRecode) {
            return Result.<Long>fail().message("非法操作！");
        }

        // 给 OrderInfo 设置 userId
        orderInfo.setUserId(Long.parseLong(userId));

        // 调用订单模块的远程接口，提交订单
        Long orderId = orderFeignClient.submitSeckillOrder(orderInfo).getData();

        // 判空保护
        if (orderId == null) {
            return Result.<Long>fail().message("保存订单失败！");
        }

        // 删除临时订单
        redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS).delete(userId);

        // 保存正式订单的 id 和用户 id 的对应关系
        redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS_USERS).put(userId, orderId.toString());

        return Result.ok(orderId);
    }

    @GetMapping("/auth/checkOrder/{skuId}")
    public Result checkOrder(@PathVariable("skuId") Long skuId, HttpServletRequest request) {

        String userId = AuthContextHolder.getUserId(request);

        return seckillGoodsService.checkOrder(skuId, userId);

    }

}
